// Package db db/connection.go 管理数据库连接
package db

import (
	"context"      // 用于带超时的 Ping 操作
	"database/sql" // 标准数据库接口
	"errors"       // 用于创建标准错误
	"fmt"          // 用于格式化字符串和错误
	"sync"         // 用于互斥锁和 RWMutex，以及 Once
	"time"         // 用于设置连接超时和监控间隔

	"px-table-checksum-go/config" // 导入配置包

	_ "github.com/go-sql-driver/mysql" // 匿名导入 MySQL 驱动，以便 database/sql 知道如何连接 MySQL
	log "github.com/sirupsen/logrus"   // 导入日志库
)

// Connection (已废弃 - 不再需要单独包装 *sql.DB)
// type Connection struct {
// 	DB *sql.DB
// 	mu sync.Mutex
// }

// DBConnectionManager 数据库连接管理器，负责管理源、目标和校验库的连接池
type DBConnectionManager struct {
	// 数据库连接池实例
	sourceDB   *sql.DB // 源数据库连接池
	targetDB   *sql.DB // 目标数据库连接池
	checksumDB *sql.DB // 校验结果数据库连接池

	// 连接状态标志 (用于快速检查，避免频繁 Ping)
	sourceConnected   bool
	targetConnected   bool
	checksumConnected bool

	// 读写锁，用于保护连接池实例和状态的并发访问
	sourceMu   sync.RWMutex // 保护 sourceDB 和 sourceConnected
	targetMu   sync.RWMutex // 保护 targetDB 和 targetConnected
	checksumMu sync.RWMutex // 保护 checksumDB 和 checksumConnected

	// 各数据库的配置信息 (从全局配置中获取引用)
	sourceConfig   *config.DBConfig
	targetConfig   *config.DBConfig
	checksumConfig *config.DBConfig

	// 停止监控通道 (用于优雅地停止监控 goroutine)
	stopMonitor chan struct{}
	monitorWg   sync.WaitGroup // 等待监控 goroutine 退出
}

// 全局数据库连接管理器实例和初始化控制
var (
	globalDBManager *DBConnectionManager // 全局单例
	managerOnce     sync.Once            // 保证初始化只执行一次
)

// GetDBManager 获取全局数据库连接管理器单例实例
func GetDBManager() *DBConnectionManager {
	managerOnce.Do(func() {
		// 首次调用时初始化
		globalDBManager = &DBConnectionManager{
			// 从全局配置中获取数据库配置的引用
			sourceConfig:   &config.GlobalConfig.DBSource,
			targetConfig:   &config.GlobalConfig.DBTarget,
			checksumConfig: &config.GlobalConfig.DBChecksum,
			stopMonitor:    make(chan struct{}), // 初始化停止通道
		}
		log.Debug("全局数据库连接管理器已初始化")
	})
	return globalDBManager
}

// InitAllPools 初始化所有配置的数据库连接池
func InitAllPools() error {
	manager := GetDBManager() // 获取单例
	var errs []error          // 用于收集初始化过程中的错误

	// 初始化源数据库连接
	if err := manager.InitSourceDB(); err != nil {
		errs = append(errs, fmt.Errorf("初始化源数据库连接失败: %w", err))
	}

	// 初始化目标数据库连接
	if err := manager.InitTargetDB(); err != nil {
		errs = append(errs, fmt.Errorf("初始化目标数据库连接失败: %w", err))
	}

	// 初始化校验数据库连接
	if err := manager.InitChecksumDB(); err != nil {
		errs = append(errs, fmt.Errorf("初始化校验数据库连接失败: %w", err))
	}

	// 如果有任何错误，返回组合错误
	if len(errs) > 0 {
		// 可以选择返回第一个错误，或者将所有错误组合起来
		// 这里返回第一个错误
		return errs[0]
	}

	log.Info("所有数据库连接池初始化成功")
	return nil
}

// InitSourceDB 初始化源数据库连接池
func (m *DBConnectionManager) InitSourceDB() error {
	m.sourceMu.Lock() // 获取写锁，防止并发初始化
	defer m.sourceMu.Unlock()

	// 如果已存在连接池，先关闭
	if m.sourceDB != nil {
		log.Info("检测到已存在的源数据库连接池，将先关闭旧连接...")
		if err := m.sourceDB.Close(); err != nil {
			log.Warnf("关闭旧源数据库连接池失败: %v", err)
			// 即使关闭失败，也继续尝试创建新连接
		}
		m.sourceDB = nil
		m.sourceConnected = false
	}

	// 检查配置是否有效
	if m.sourceConfig == nil || m.sourceConfig.Host == "" {
		return errors.New("源数据库配置无效或未加载")
	}

	log.Infof("尝试初始化源数据库连接: %s", m.sourceConfig.ID())
	// 使用配置中的 DSN 打开数据库连接
	db, err := sql.Open("mysql", m.sourceConfig.DSN())
	if err != nil {
		log.Errorf("打开源数据库连接失败 (%s): %v", m.sourceConfig.ID(), err)
		return fmt.Errorf("打开源数据库连接失败 (%s): %w", m.sourceConfig.ID(), err)
	}

	// 设置连接池参数
	db.SetMaxOpenConns(m.sourceConfig.MaxOpenConns)
	db.SetMaxIdleConns(m.sourceConfig.MaxIdleConns)
	db.SetConnMaxLifetime(time.Duration(m.sourceConfig.ConnMaxLifetime) * time.Second)

	// 使用带超时的 PingContext 验证连接是否成功
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) // 5秒超时
	defer cancel()

	if err := db.PingContext(ctx); err != nil {
		log.Errorf("源数据库连接验证 (Ping) 失败 (%s): %v", m.sourceConfig.ID(), err)
		_ = db.Close() // 打开成功但 Ping 失败，需要关闭连接池
		m.sourceConnected = false
		return fmt.Errorf("源数据库连接验证失败 (%s): %w", m.sourceConfig.ID(), err)
	}

	// 连接成功
	m.sourceDB = db
	m.sourceConnected = true
	log.Infof("源数据库连接初始化成功: %s", m.sourceConfig.ID())

	// 启动连接池状态监控 goroutine
	m.monitorWg.Add(1) // 增加 WaitGroup 计数
	go m.monitorConnectionPool(m.sourceDB, "source", m.sourceConfig.ID(), &m.sourceMu, &m.sourceConnected, m.stopMonitor, &m.monitorWg)

	return nil
}

// InitTargetDB 初始化目标数据库连接池 (逻辑与 InitSourceDB 类似)
func (m *DBConnectionManager) InitTargetDB() error {
	m.targetMu.Lock()
	defer m.targetMu.Unlock()

	if m.targetDB != nil {
		log.Info("检测到已存在的目标数据库连接池，将先关闭旧连接...")
		if err := m.targetDB.Close(); err != nil {
			log.Warnf("关闭旧目标数据库连接池失败: %v", err)
		}
		m.targetDB = nil
		m.targetConnected = false
	}

	if m.targetConfig == nil || m.targetConfig.Host == "" {
		return errors.New("目标数据库配置无效或未加载")
	}

	log.Infof("尝试初始化目标数据库连接: %s", m.targetConfig.ID())
	db, err := sql.Open("mysql", m.targetConfig.DSN())
	if err != nil {
		log.Errorf("打开目标数据库连接失败 (%s): %v", m.targetConfig.ID(), err)
		return fmt.Errorf("打开目标数据库连接失败 (%s): %w", m.targetConfig.ID(), err)
	}

	db.SetMaxOpenConns(m.targetConfig.MaxOpenConns)
	db.SetMaxIdleConns(m.targetConfig.MaxIdleConns)
	db.SetConnMaxLifetime(time.Duration(m.targetConfig.ConnMaxLifetime) * time.Second)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := db.PingContext(ctx); err != nil {
		log.Errorf("目标数据库连接验证 (Ping) 失败 (%s): %v", m.targetConfig.ID(), err)
		_ = db.Close()
		m.targetConnected = false
		return fmt.Errorf("目标数据库连接验证失败 (%s): %w", m.targetConfig.ID(), err)
	}

	m.targetDB = db
	m.targetConnected = true
	log.Infof("目标数据库连接初始化成功: %s", m.targetConfig.ID())

	m.monitorWg.Add(1)
	go m.monitorConnectionPool(m.targetDB, "target", m.targetConfig.ID(), &m.targetMu, &m.targetConnected, m.stopMonitor, &m.monitorWg)

	return nil
}

// InitChecksumDB 初始化校验数据库连接池 (逻辑与 InitSourceDB 类似)
func (m *DBConnectionManager) InitChecksumDB() error {
	m.checksumMu.Lock()
	defer m.checksumMu.Unlock()

	if m.checksumDB != nil {
		log.Info("检测到已存在的校验数据库连接池，将先关闭旧连接...")
		if err := m.checksumDB.Close(); err != nil {
			log.Warnf("关闭旧校验数据库连接池失败: %v", err)
		}
		m.checksumDB = nil
		m.checksumConnected = false
	}

	if m.checksumConfig == nil || m.checksumConfig.Host == "" {
		return errors.New("校验数据库配置无效或未加载")
	}

	log.Infof("尝试初始化校验数据库连接: %s", m.checksumConfig.ID())
	db, err := sql.Open("mysql", m.checksumConfig.DSN()) // 使用 DSN() 获取连接字符串
	if err != nil {
		log.Errorf("打开校验数据库连接失败 (%s): %v", m.checksumConfig.ID(), err)
		return fmt.Errorf("打开校验数据库连接失败 (%s): %w", m.checksumConfig.ID(), err)
	}

	db.SetMaxOpenConns(m.checksumConfig.MaxOpenConns)
	db.SetMaxIdleConns(m.checksumConfig.MaxIdleConns)
	db.SetConnMaxLifetime(time.Duration(m.checksumConfig.ConnMaxLifetime) * time.Second)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	if err := db.PingContext(ctx); err != nil {
		log.Errorf("校验数据库连接验证 (Ping) 失败 (%s): %v", m.checksumConfig.ID(), err)
		_ = db.Close()
		m.checksumConnected = false
		return fmt.Errorf("校验数据库连接验证失败 (%s): %w", m.checksumConfig.ID(), err)
	}

	m.checksumDB = db
	m.checksumConnected = true
	log.Infof("校验数据库连接初始化成功: %s", m.checksumConfig.ID())

	m.monitorWg.Add(1)
	go m.monitorConnectionPool(m.checksumDB, "checksum", m.checksumConfig.ID(), &m.checksumMu, &m.checksumConnected, m.stopMonitor, &m.monitorWg)

	return nil
}

// GetSourceDB 获取源数据库连接池实例 (*sql.DB)
// 使用读锁保护并发访问。
func (m *DBConnectionManager) GetSourceDB() *sql.DB {
	m.sourceMu.RLock()         // 获取读锁
	defer m.sourceMu.RUnlock() // 保证读锁被释放

	if !m.sourceConnected || m.sourceDB == nil {
		// 这里不进行自动重连，让调用者处理连接不可用的情况
		// 或者可以取消注释下面的 fatal log
		log.Error("获取源数据库连接失败：连接未初始化或不可用")
		// log.Fatal("源数据库连接池未初始化或连接不可用") // 直接让程序退出可能不是最好的方式
		return nil // 返回 nil，让调用者检查
	}

	return m.sourceDB
}

// GetTargetDB 获取目标数据库连接池实例 (*sql.DB)
func (m *DBConnectionManager) GetTargetDB() *sql.DB {
	m.targetMu.RLock()
	defer m.targetMu.RUnlock()
	if !m.targetConnected || m.targetDB == nil {
		log.Error("获取目标数据库连接失败：连接未初始化或不可用")
		return nil
	}
	return m.targetDB
}

// GetChecksumDB 获取校验数据库连接池实例 (*sql.DB)
func (m *DBConnectionManager) GetChecksumDB() *sql.DB {
	m.checksumMu.RLock()
	defer m.checksumMu.RUnlock()
	if !m.checksumConnected || m.checksumDB == nil {
		log.Error("获取校验数据库连接失败：连接未初始化或不可用")
		return nil
	}
	return m.checksumDB
}

// CloseAllPools 安全地关闭所有数据库连接池
func (m *DBConnectionManager) CloseAllPools() {
	log.Info("开始关闭所有数据库连接池...")

	// 发送停止信号给所有监控 goroutine
	close(m.stopMonitor)

	// 关闭各个连接池
	m.CloseSourceDB()
	m.CloseTargetDB()
	m.CloseChecksumDB()

	// 等待所有监控 goroutine 退出
	m.monitorWg.Wait()
	log.Info("所有数据库连接池已关闭，监控 goroutine 已退出")
}

// CloseSourceDB 安全地关闭源数据库连接池
func (m *DBConnectionManager) CloseSourceDB() {
	m.sourceMu.Lock() // 获取写锁
	defer m.sourceMu.Unlock()

	if m.sourceDB != nil {
		log.Infof("正在关闭源数据库连接池 (%s)...", m.sourceConfig.ID())
		if err := m.sourceDB.Close(); err != nil {
			log.Warnf("关闭源数据库连接池错误 (%s): %v", m.sourceConfig.ID(), err)
		} else {
			log.Info("源数据库连接池已关闭")
		}
		m.sourceDB = nil          // 清理引用
		m.sourceConnected = false // 更新状态
	} else {
		log.Debug("源数据库连接池已经是关闭状态或未初始化")
	}
}

// CloseTargetDB 安全地关闭目标数据库连接池
func (m *DBConnectionManager) CloseTargetDB() {
	m.targetMu.Lock()
	defer m.targetMu.Unlock()

	if m.targetDB != nil {
		log.Infof("正在关闭目标数据库连接池 (%s)...", m.targetConfig.ID())
		if err := m.targetDB.Close(); err != nil {
			log.Warnf("关闭目标数据库连接池错误 (%s): %v", m.targetConfig.ID(), err)
		} else {
			log.Info("目标数据库连接池已关闭")
		}
		m.targetDB = nil
		m.targetConnected = false
	} else {
		log.Debug("目标数据库连接池已经是关闭状态或未初始化")
	}
}

// CloseChecksumDB 安全地关闭校验数据库连接池
func (m *DBConnectionManager) CloseChecksumDB() {
	m.checksumMu.Lock()
	defer m.checksumMu.Unlock()

	if m.checksumDB != nil {
		log.Infof("正在关闭校验数据库连接池 (%s)...", m.checksumConfig.ID())
		if err := m.checksumDB.Close(); err != nil {
			log.Warnf("关闭校验数据库连接池错误 (%s): %v", m.checksumConfig.ID(), err)
		} else {
			log.Info("校验数据库连接池已关闭")
		}
		m.checksumDB = nil
		m.checksumConnected = false
	} else {
		log.Debug("校验数据库连接池已经是关闭状态或未初始化")
	}
}

// monitorConnectionPool 定期监控数据库连接池的状态和健康状况
func (m *DBConnectionManager) monitorConnectionPool(db *sql.DB, name string, id string, mu *sync.RWMutex, connected *bool, stop <-chan struct{}, wg *sync.WaitGroup) {
	defer wg.Done() // 确保 WaitGroup 计数器减少
	if db == nil {
		log.Warnf("数据库连接池 [%s - %s] 为空，无法监控", name, id)
		return
	}

	ticker := time.NewTicker(30 * time.Second) // 每 30 秒检查一次
	defer ticker.Stop()                        // 停止 ticker

	log.Debugf("启动数据库连接池监控 [%s - %s]", name, id)

	for {
		select {
		case <-ticker.C: // 定时器触发
			// 记录连接池统计信息
			stats := db.Stats()
			log.Debugf("连接池状态 [%s - %s]: 活跃=%d, 空闲=%d, 等待=%d, 最大生命周期关闭=%d",
				name, id, stats.InUse, stats.Idle, stats.WaitCount, stats.MaxLifetimeClosed)

			// 检查数据库连接是否仍然可用 (Ping)
			ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) // 短超时Ping
			err := db.PingContext(ctx)
			cancel() // 及时释放资源

			mu.Lock() // 获取写锁以更新状态
			if err != nil {
				log.Warnf("数据库连接检查失败 [%s - %s]: %v", name, id, err)
				if *connected { // 如果之前是连接状态，现在失败了
					*connected = false // 更新状态为未连接
					log.Errorf("数据库连接丢失 [%s - %s]", name, id)
					// 可以在这里触发告警或尝试自动重连逻辑（但自动重连逻辑更适合放在 Get 方法中按需触发）
				}
			} else {
				if !*connected { // 如果之前是断开状态，现在恢复了
					*connected = true // 更新状态为已连接
					log.Infof("数据库连接恢复 [%s - %s]", name, id)
				}
			}
			mu.Unlock() // 释放写锁

		case <-stop: // 收到停止信号
			log.Debugf("停止数据库连接池监控 [%s - %s]", name, id)
			return // 退出 goroutine
		}
	}
}

// IsSourceHealthy 检查源数据库连接是否健康 (通过 Ping)
func (m *DBConnectionManager) IsSourceHealthy() bool {
	m.sourceMu.RLock() // 获取读锁
	db := m.sourceDB
	connected := m.sourceConnected
	m.sourceMu.RUnlock() // 释放读锁

	if !connected || db == nil {
		return false // 未连接或连接池为空
	}

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) // 短超时 Ping
	defer cancel()
	err := db.PingContext(ctx)
	if err != nil {
		log.Warnf("源数据库健康检查失败: %v", err)
		// 可以在这里考虑更新 m.sourceConnected 状态（需要写锁）
		return false
	}
	return true
}

// IsTargetHealthy 检查目标数据库连接是否健康
func (m *DBConnectionManager) IsTargetHealthy() bool {
	m.targetMu.RLock()
	db := m.targetDB
	connected := m.targetConnected
	m.targetMu.RUnlock()

	if !connected || db == nil {
		return false
	}

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	err := db.PingContext(ctx)
	if err != nil {
		log.Warnf("目标数据库健康检查失败: %v", err)
		return false
	}
	return true
}

// IsChecksumHealthy 检查校验数据库连接是否健康
func (m *DBConnectionManager) IsChecksumHealthy() bool {
	m.checksumMu.RLock()
	db := m.checksumDB
	connected := m.checksumConnected
	m.checksumMu.RUnlock()

	if !connected || db == nil {
		return false
	}

	ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
	defer cancel()
	err := db.PingContext(ctx)
	if err != nil {
		log.Warnf("校验数据库健康检查失败: %v", err)
		return false
	}
	return true
}

// GetSourceStats 获取源数据库连接池的统计信息
func (m *DBConnectionManager) GetSourceStats() sql.DBStats {
	m.sourceMu.RLock()
	defer m.sourceMu.RUnlock()
	if m.sourceDB == nil {
		return sql.DBStats{} // 返回空统计信息
	}
	return m.sourceDB.Stats()
}

// GetTargetStats 获取目标数据库连接池的统计信息
func (m *DBConnectionManager) GetTargetStats() sql.DBStats {
	m.targetMu.RLock()
	defer m.targetMu.RUnlock()
	if m.targetDB == nil {
		return sql.DBStats{}
	}
	return m.targetDB.Stats()
}

// GetChecksumStats 获取校验数据库连接池的统计信息
func (m *DBConnectionManager) GetChecksumStats() sql.DBStats {
	m.checksumMu.RLock()
	defer m.checksumMu.RUnlock()
	if m.checksumDB == nil {
		return sql.DBStats{}
	}
	return m.checksumDB.Stats()
}

// QuerySourceWithContext 使用上下文执行源数据库查询
func (m *DBConnectionManager) QuerySourceWithContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
	db := m.GetSourceDB() // 使用 Get 方法获取连接池
	if db == nil {
		return nil, errors.New("源数据库连接不可用")
	}
	return db.QueryContext(ctx, query, args...)
}

// QueryTargetWithContext 使用上下文执行目标数据库查询
func (m *DBConnectionManager) QueryTargetWithContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
	db := m.GetTargetDB()
	if db == nil {
		return nil, errors.New("目标数据库连接不可用")
	}
	return db.QueryContext(ctx, query, args...)
}

// QueryChecksumWithContext 使用上下文执行校验数据库查询
func (m *DBConnectionManager) QueryChecksumWithContext(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) {
	db := m.GetChecksumDB()
	if db == nil {
		return nil, errors.New("校验数据库连接不可用")
	}
	return db.QueryContext(ctx, query, args...)
}

// ExecSourceWithContext 使用上下文执行源数据库更新操作
func (m *DBConnectionManager) ExecSourceWithContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
	db := m.GetSourceDB()
	if db == nil {
		return nil, errors.New("源数据库连接不可用")
	}
	return db.ExecContext(ctx, query, args...)
}

// ExecTargetWithContext 使用上下文执行目标数据库更新操作
func (m *DBConnectionManager) ExecTargetWithContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
	db := m.GetTargetDB()
	if db == nil {
		return nil, errors.New("目标数据库连接不可用")
	}
	return db.ExecContext(ctx, query, args...)
}

// ExecChecksumWithContext 使用上下文执行校验数据库更新操作
func (m *DBConnectionManager) ExecChecksumWithContext(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
	db := m.GetChecksumDB()
	if db == nil {
		return nil, errors.New("校验数据库连接不可用")
	}
	return db.ExecContext(ctx, query, args...)
}
